<?php

declare(strict_types=1);

namespace App\User\Service;

use App\Common\Constants\ErrorCode;
use App\Common\Constants\Stakeholder;
use App\Common\Service\BaseService;
use App\Order\Model\OrderModel;
use App\Order\Service\OrderService;
use App\Resource\Model\TeamLeaderModel;
use App\Resource\Service\StoreService;
use App\Third\Service\Kz\KzMainService;
use App\Order\Service\Order\OrderMiniService;
use App\User\Model\AddressModel;
use App\User\Model\MemberModel;
use EasyWeChat\Factory;
use EasyWeChat\Kernel\Exceptions\DecryptException;
use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Hyperf\Database\Model\Builder;
use Hyperf\Database\Model\Collection;
use Hyperf\Database\Model\Model;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Guzzle\CoroutineHandler;

class MemberService extends BaseService
{

    /**
     * @Inject()
     * @var TagService
     */
    private $tagService;

    /**
     * @Inject()
     * @var KzMainService
     */
    private $kzMainService;

    /**
     * 获取所有会员列表
     * @param array $params
     * @param array $field
     *
     * @return mixed
     */
    public function getMemberList(array $params = [], array $field = ['*'])
    {
        $params = $this->conditionParams($params);

        // 标签名称
        $tagArr = $this->tagService->getAllTagName();
        $orderCount = OrderModel::query()
            ->selectRaw(
                'COUNT(1) as buy_num, 
                SUM(total_price) as total_amount, 
                ROUND(AVG(total_price)) as unit_price, 
                DATEDIFF(CURDATE(), MAX(pay_at)) as without_order_day,
                mid'
            )
            ->where([
                ['is_pay', '=', Stakeholder::ORDER_PAID]
            ])
            ->when($params['paid_time'] ?? 0, function ($query, $paid_time) {
                return $query->whereBetween('pay_at', $paid_time);
            })->groupBy(['mid']);
        $members = MemberModel::query()
            ->when($params['phone'] ?? 0, function ($query, $phone) {
                return $query->where('phone', $phone);
            })
            ->when(is_numeric($params['source']), function ($query) use ($params) {
                return $query->where('source', $params['source']);
            })
            ->when($params['tags'] ?? 0, function ($query, $tags) {
                return $query->whereRaw('INSTR(tags, ?) > 0', [$tags]);
            })
            ->when($params['register_date'] ?? 0, function ($query, $register_date) {
                return $query->whereBetween('register_date', $register_date);
            })
            ->when(is_numeric($params['gender']), function ($query) use ($params) {
                return $query->where('gender', $params['gender']);
            })
            ->when($params['age'] ?? 0, function ($query, $age) {
                $birthday = $this->getDateRangeByAge($age);
                return $query->where([
                    ['birthday', '>', $birthday['min']],
                    ['birthday', '<', $birthday['max']]
                ]);
            })
            ->selectRaw(implode(',', $field))
            ->when($params['paid_time'] ?? 0, function ($query) use($orderCount){
                return $query->joinSub($orderCount, 'order', function ($join) {
                    $join->on('store_member.mid', '=', 'order.mid');
                });
            }, function ($query) use($orderCount) {
                return $query->leftJoinSub($orderCount, 'order', function ($join) {
                    $join->on('store_member.mid', '=', 'order.mid');
                });
            })
            ->when($params['without_order_day'] ?? 0, function ($query, $without_order_day) {
                return $query->whereBetween('without_order_day', $without_order_day);
            })
            ->when($params['buy_num'] ?? 0, function ($query, $buy_num) {
                return $query->whereBetween('buy_num', $buy_num);
            })
            ->when($params['total_amount'] ?? 0, function ($query, $total_amount) {
                return $query->whereBetween('total_amount', $total_amount);
            })
            ->when($params['unit_price'] ?? 0, function ($query, $unit_price) {
                return $query->whereBetween('unit_price', $unit_price);
            })
            ->latest('mid')
            ->when($params['perpage'] ?? 0, function ($query, $perPage) {
                return $query->paginate((int)$perPage);
            }, function ($query) {
                return $query->get();
            });
        foreach ($members as $k => $v) {
            $members[$k]['buy_num'] = $v['buy_num'] ?? 0;
            $members[$k]['total_amount'] = $v['total_amount'] ?? 0;
            $members[$k]['unit_price'] = $v['unit_price'] ?? 0;
            $members[$k]['tags'] = implode(',', array_intersect_key($tagArr, array_flip(explode(',', $v['tags']))));
            $members[$k]['recharge_amount'] = 0;
            $members[$k]['recharge_times'] = 0;
            $members[$k]['level'] = 0;
        }
        return $members;
    }

    /**
     * @param array $params
     *
     * @return array
     */
    public function conditionParams(array $params)
    {
        $registerDate = $params['register_date'] ?? '';
        if (!empty($params['min_age']) && !empty($params['max_age'])) {
            $params['age'] = $params['min_age'] . '-' . $params['max_age'];
        }
        if (!empty($params['tags']) && is_array($params['tags'])) {
            $params['tags'] = implode(',', $params['tags']);
        }
        if (!empty($params['min_without_order_day']) && !empty($params['max_without_order_day'])) {
            $params['without_order_day'] = [$params['min_without_order_day'], $params['max_without_order_day']];
        }
        if (is_numeric($params['min_buy_num']) && is_numeric($params['max_buy_num'])) {
            $params['buy_num'] = [$params['min_buy_num'], $params['max_buy_num']];
        }
        if (!empty($params['min_total_amount']) && !empty($params['max_total_amount'])) {
            $params['total_amount'] = [$params['min_total_amount'], $params['max_total_amount']];
        }
        if (!empty($params['min_unit_price']) && !empty($params['max_unit_price'])) {
            $params['unit_price'] = [$params['min_unit_price'], $params['max_unit_price']];
        }
        if (!empty($registerDate) && strlen(trim($registerDate)) == 10){
            $params['register_date'] = [
                trim($registerDate),
                trim($registerDate) . ' 23:59:59'
            ];
        }
        if (!empty($registerDate) && strlen(trim($registerDate)) > 11) {
            $params['register_date'] = [
                trim(substr($registerDate, 0, 10)),
                trim(substr($registerDate, -10)) . ' 23:59:59'
            ];
        }
        if (!empty($params['paid_time']) && strlen(trim($params['paid_time'])) > 11) {
            $params['paid_time'] = [
                trim(substr($params['paid_time'], 0, 10)),
                trim(substr($params['paid_time'], -10)) . ' 23:59:59'
            ];
        }
        return $params;
    }

    /**
     * 获取会员详情信息
     * @param array $where
     * @param array|string[] $field
     *
     * @return array|Builder|Collection|Model
     */
    public function find(array $where, array $field = ['*'])
    {
        // 标签名称
        $tagArr = $this->tagService->getAllTagName();
        $detail = $this->findFirst($where, $field);
        if (!empty($detail)) {
            if (isset($detail->tags)) {
                $detail->tags = implode(',', array_intersect_key($tagArr, array_flip(explode(',', $detail->tags))));
            }
            if (isset($detail->kz_cus_code)) {
                $kzInfo = $this->kzMainService->getCusInfo($detail->kz_cus_code)['data'];
                $balance = $kzInfo['cusInfo']['accountMoney']/100 ?? 0;
                $detail->balance = number_format((float)$balance, 2, '.', '');
            }
            $detail->total_payment = $this->MemberTotalPayment($detail->mid);
        }
        return $detail;
    }

    /**
     * 查询会员信息
     * @param array $where
     * @param array|string[] $field
     * @return array|Builder|Model|object
     */
    public function findFirst(array $where, array $field = ['*']){
        return MemberModel::query()->where($where)->select($field)->first() ?? [];
    }

    /**
     * 查找特定字段
     * @param array $where
     * @param string $field
     * @return mixed|void|null
     */
    public function findValue(array $where, string $field)
    {
        return MemberModel::query()->where($where)->value($field);
    }

    /**
     * 更新数据
     * @param int $id
     * @param array $update
     *
     * @return int
     */
    public function update(int $id, array $update = [])
    {
        $member = MemberModel::query()->find($id);
        foreach ($update as $k => $v) {
            $member->$k = $v;
        }
        return $member->save($update);
    }

    /**
     * 多会员信息查询
     * @param array $ids
     * @param array|string[] $field
     *
     * @return Builder|Builder[]|Collection|Model|null
     */
    public function moreMemberInfoByIds(array $ids = [], array $field = ['*'])
    {
        return MemberModel::query()
            ->whereIn('mid', $ids)
            ->get($field)
            ->toArray();
    }

    /**
     * 特定标签会员数
     * @param $tag_id
     *
     * @return int
     */
    public function getMemberNumByTag(int $tag_id)
    {
        return MemberModel::query()->whereRaw('INSTR(tags, ?) > 0', [$tag_id])->count();
    }

    /**
     * 会员累积消费金额
     * @param int $mid
     *
     * @return int|mixed
     */
    public function MemberTotalPayment(int $mid)
    {
        return $this->container->get(OrderService::class)->getMemberTotalPayment($mid);
    }

    /**
     * 用户概览---新增会员数
     * @param array $params
     *
     * @return int|mixed
     */
    public function newMemberCount(array $params)
    {
        $basic = MemberModel::query()
            ->whereBetween('register_date', $params['date'])
            ->pluck('mid')
            ->toArray();

        $contrast = MemberModel::query()
            ->whereBetween('register_date', $params['contrasttime'])
            ->pluck('mid')
            ->toArray();

        $basicCount = count($basic);
        $contrastCount = count($contrast);

        $trend = $this->coefficientsAndTrends((string)$basicCount, (string)$contrastCount);

        return [
            'data' => [
                'amt' => $basicCount,
                'changeFlag' => $trend['changeFlag'],
                'ratio' => $trend['ratio']
            ],
            'combination' => [
                'basic' => $basic,
                'contrast' => $contrast
            ]
        ];
    }


    /**
     * 用户概览---新增会员数(折线图)
     * @param array $params
     *
     * @return int|mixed
     */
    public function newMemberCountGroupByDate(array $params)
    {
        if ($params['flag'] == 1) {
            $selectRaw = "DATE_FORMAT(register_date, '%Y-%m-%d') as date,count(mid) as newMemberNums";
        } else {
            $selectRaw = "DATE_FORMAT(register_date, '%Y-%m') as date,count(mid) as newMemberNums";
        }
        $basic = MemberModel::query()
            ->selectRaw($selectRaw)
            ->whereBetween('register_date', $params['date'])
            ->groupBy(['date'])
            ->get()
            ->toArray();

        $contrast = MemberModel::query()
            ->selectRaw($selectRaw)
            ->whereBetween('register_date', $params['contrasttime'])
            ->groupBy(['date'])
            ->get()
            ->toArray();

        return ['basic' => $basic, 'contrast' => $contrast];

    }

    /**
     * 会员来源渠道
     * @param array $params
     *
     * @return int|mixed
     */
    public function operateMemberSource(array $params)
    {
        $sourcePlatform = MemberModel::query()
            ->selectRaw("count(mid) as newMemberNums,source as platform")
            ->whereBetween('register_date', $params['date'])
            ->groupBy(['platform'])
            ->get()
            ->toArray();

        $accumulationMember = MemberModel::query()
            ->selectRaw("count(mid) as memberNums,source as platform")
            ->groupBy(['platform'])
            ->get()
            ->toArray();
        return ['sourcePlatform' => $sourcePlatform, 'accumulationMember' => $accumulationMember];
    }

    /**
     * 所有会员数
     *
     * @return int|mixed
     */
    public function getAllMemberCount()
    {
        return MemberModel::query()->count('mid');
    }

    /**
     * 获取时间段新增会员
     * @param array $params
     *
     * @return int|mixed
     */
    public function getNewMemberMidArr(array $params)
    {
        return MemberModel::query()
            ->whereBetween('register_date', $params['date'])
            ->pluck('mid')
            ->toArray();
    }


    /**
     * 个人中心主数据
     * @param array $params
     * @return array
     * @author liule
     */
    public function mainData(array $params){
        $where = ['mid' => $params['mid']];
        $field = ['mid','phone','nickname','gender','birthday','openid','face_url','kz_cus_code'];
        $detail = $this->findFirst($where, $field);
        if (!empty($detail)) {
            // 团长信息
            $teamInfo = TeamLeaderModel::query()->where($where)->first();
            $detail->leader_id = $teamInfo->id ?? '';
            $detail->leader_status = $teamInfo->status ?? '';
            $detail->ticketCount = 0;
            $detail->balanceMoney = 0;
            // 订单角标
            $order_count = $this->container->get(OrderService::class)->orderCountSubscript((int)$params['mid']);

            if (!empty($detail->kz_cus_code)) {
                $kzInfo = $this->kzMainService->getCusInfo($detail->kz_cus_code)['data'];
                $balance = $kzInfo['cusInfo']['accountMoney'] ?? 0;
                $ticketCount = count($kzInfo['ticketInfo']) ?? 0;
                $detail->ticketCount = $ticketCount;
                $detail->balanceMoney = $balance;
            }
        }else{
            return ['code' => 0, 'errorCode' => ErrorCode::SYSTEM_INVALID, 'msg' => '会员信息异常'];
        }
        // 客服电话
        $servicePhone = $this->container->get(StoreService::class)->storeConfig()['service_phone'];
        // 我的免单
        $my_free_list = $this->container->get(OrderMiniService::class)->getFreeCenter($params['mid'])->items();
        $data = ['member_info' => $detail, 'order_count' => $order_count, 'service_phone' => $servicePhone, 'my_free_list' => $my_free_list];
        return ['code' => 1, 'msg' => '个人中心数据获取成功', 'data' => $data];
    }

    /**
     * 授权登录
     * @param string $code
     * @param string $iv
     * @param string $encryptedData
     * @param int $bdUid
     * @return array
     * @throws DecryptException
     * @throws InvalidConfigException
     * @author liule
     */
    public function getWechatUserInfoByAuth(string $code, string $iv, string $encryptedData, int $bdUid, $uinfo)
    {
        $uinfo = json_decode($uinfo, true);
        $options        = [
            'app_id' => config('wechat.app_id'),
            'secret' => config('wechat.secret')
        ];
        $app = Factory::miniProgram($options);
        $handler =new CoroutineHandler();
        // 设置 HttpClient，部分接口直接使用了 http_client。
        $config = $app['config']->get('http', []);
        $config['handler'] = $stack = HandlerStack::create($handler);
        $app->rebind('http_client', new Client($config));
        // 部分接口在请求数据时，会根据 guzzle_handler 重置 Handler
        $app['guzzle_handler'] = $handler;
        $sessionInfo = $app->auth->session($code);
        if (empty($sessionInfo['session_key'])) {
            return ['code' => 0,'errorCode' => ErrorCode::SESSION_CODE_ERROR];
        }
        $weChatUserInfo = $app->encryptor->decryptData($sessionInfo['session_key'], $iv, $encryptedData);
        $weChatUserInfo = [
            "openid" => array_key_exists('openid', $sessionInfo) ? $sessionInfo['openid'] : $weChatUserInfo['openId'],
            "unionId" => array_key_exists('unionid', $sessionInfo) ? $sessionInfo['unionid'] : $weChatUserInfo['unionId'],
            "s_key" => $sessionInfo['session_key'],
            "token" => md5(array_key_exists('openid', $sessionInfo) ? $sessionInfo['openid'] : $weChatUserInfo['openId']),
            'nickname' => $uinfo['nickName'],
            'face_url' => $uinfo['avatarUrl'],
            'gender' => $uinfo['gender'],
            'ground_promotion_user_id' => $bdUid,
            'register_address' => $uinfo['province'].'--'.$uinfo['city']
        ];

        $data = [
            'encode' => base64_encode(http_build_query($weChatUserInfo))
        ];
        return ['code' => 1, 'msg' => '用户信息获取成功', 'data' => $data];
    }

    /**
     * 获取微信手机号
     * @param string $iv
     * @param string $encryptedData
     * @param string $encode
     * @return array
     * @throws DecryptException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function getMemberInfoByWeChatPhone(string $iv, string $encryptedData, string $encode){
        parse_str(base64_decode($encode), $weChatUserInfo);
        $options        = [
            'app_id' => config('wechat.app_id'),
            'secret' => config('wechat.secret')
        ];
        $app = Factory::miniProgram($options);
        $decryptedData = $app->encryptor->decryptData($weChatUserInfo['s_key'], $iv, $encryptedData);
        $phone = $decryptedData['phoneNumber'] ?? '';
        if (empty($phone)){
            return ['code' => 0,'errorCode' => ErrorCode::SYSTEM_INVALID, 'msg' => '微信手机号获取失败，请输入手机号登录'];
        }
        return $this->memberLoginDo($phone, $weChatUserInfo);
    }

    /**
     * 用户输入手机号登录
     * @param string $phone
     * @param string $code
     * @param string $encode
     * @return array
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @author liule
     */
    public function memberLoginByInputPhone(string $phone, string $code, string $encode){
        $cachePhoneCode = $this->smsRedis->get('sms-' . $phone);
        if (!$cachePhoneCode or  $code != $cachePhoneCode) {
            return ['code' => 0,'errorCode' => ErrorCode::PHONE_CODE_INVALID];
        }
        parse_str(base64_decode($encode), $weChatUserInfo);
        return $this->memberLoginDo($phone, $weChatUserInfo);
    }

    /**
     * 小程序登录
     * @param string $phone
     * @param array $weChatUserInfo
     * @return array
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @author liule
     */
    public function memberLoginDo(string $phone, array $weChatUserInfo){
        // 手机号查询用户
        $membInfo = $this->findFirst(['phone' => $phone,'openid' => $weChatUserInfo['openid']]);
        if (!$membInfo){
            $res = $this->addCusInfo($weChatUserInfo, $phone);
            if ($res['code'] == 0){
                return $res;
            }
            $weChatUserInfo['phone'] = $phone;
            $weChatUserInfo['kz_cus_code'] = $res['data']['cusCode'];
            $membInfo = MemberModel::query()->create($weChatUserInfo);
        }else{
            $fill = [
                's_key' => $weChatUserInfo['s_key'],
                'nickname' => $weChatUserInfo['nickname'],
                'face_url' => $weChatUserInfo['face_url'],
                'gender' => $weChatUserInfo['gender'],
                'ground_promotion_user_id' => $weChatUserInfo['ground_promotion_user_id'],
                'register_address' => $weChatUserInfo['register_address'],
            ];
            // 是否有客至码
            if (empty($membInfo->kz_cus_code)){
                $res = $this->addCusInfo($membInfo->toArray(), $phone);
                if ($res['code'] == 0){
                    return $res;
                }
                $fill['kz_cus_code'] = $res['data']['cusCode'];
            }else{
                // 检测客至码是否一致
                $res = $this->kzMainService->getCusInfo('', $phone);
                if ($res['code'] == 0){
                    return $res;
                }
                if ($membInfo->kz_cus_code != $res['data']['cusInfo']['cusCode']){
                    $fill['kz_cus_code'] = $res['data']['cusInfo']['cusCode'];
                }
            }
            $membInfo->fill($fill);
            $membInfo->save();
        }
        $token = $this->jwt->setScene('miniProgram')->getToken([
            'mid' => $membInfo->mid,
            'nickname' => $membInfo->nickname,
            'cusCode' => $membInfo->kz_cus_code
        ]);
        $data = [
            'member' => [
                'mid' => $membInfo->mid,
                'leader_id' => $membInfo->leader->id ?? '',
                'leader_status' => $membInfo->leader->status ?? ''
            ],
            'token' => $token,
            'exp' => $this->jwt->setScene('miniProgram')->getTTL()
        ];
        // 进店统计
        $this->comeInToShop();
        return ['code' => 1, 'msg' => '登录成功', 'data' => $data];
    }

    /**
     * @param array $membInfo
     * @param string $phone
     * @return array
     * @author liule
     */
    public function addCusInfo(array $membInfo, string $phone){
        $shopCode = '';
        $shopName = '';
        $cusName = $membInfo['nickname'];
        $gender = $membInfo['gender'] > 0 ? ($membInfo['gender'] - 1) : '';
        $birthday = isset($membInfo['birthday']) ? date('yyyymmdd', strtotime($membInfo['birthday'])) : '';
        $avatarAddr = $membInfo['face_url'] ? :'';
        $res = $this->kzMainService->addCusInfo($phone,$cusName,$shopCode,$shopName,$gender,$birthday,$avatarAddr);
        if ($res['code'] == 0){
            $res['msg'] = '会员系统异常请稍后再试~';
            return $res;
        }
        return $res;
    }

    /**
     * 进店人数统计
     * @author liule
     */
    public function comeInToShop(){
        $key = 'homeAnalyse:' . date('Ymd');
        $redis = $this->redis;
        $redis->incr($key);
        $ttl = $redis->ttl($key);
        if (in_array($ttl, [-1, -2])){
            $diff = strtotime(date('Ymd') . ' +1 day') - time();
            $redis->expire($key, $diff);
        }
    }
    public function getShopMemberList( int$perPage,array $field = ['*'])
    {
        return MemberModel::where(['is_deleted' => 0])->select($field)->orderBy('register_date', 'desc')->paginate($perPage);
    }

    /**
     * 获取单个地址明细
     * @param int $id
     * @return array
     */
    public function getAddressInfo(array $where,array $field)
    {
        $addressArr = AddressModel::query()->where($where)->select($field)->first();
        return $addressArr?$addressArr->toArray():[];
    }
}
